import 'package:my_flutter_core/models/user.dart';
import 'package:my_flutter_core/services/route_map_service.dart';

/// IMPORTANT!!!
/// Always keep it in mind that permission / authentication / authorization MUST be applied on server side first.
/// Any client side check is for user experience ONLY.
/// NEVER TRUST CLIENT!
class RouteMapManager {
  static const String urlSingleWildcard = '/*';
  static const String urlDoubleWildcard = '/**';
  static const String denyAll = 'denyAll';
  static const String permitAll = 'permitAll';
  static const String isAuthenticatedPrefix = 'isAuthenticated';
  static const String isAuthenticated = '$isAuthenticatedPrefix()';
  static const String hasRolePrefix = 'hasRole';
  static const String hasAnyRolePrefix = 'hasAnyRole';
  static const String hasNoRolePrefix = 'hasNoRole';
  static const String hasNoAnyRolePrefix = 'hasNoAnyRole';

  /// Returns the expression like hasRole('ROLE_ADMIN').
  /// [roleAuthority] is the string of role authority. AKA role name.
  static String hasRole(String roleAuthority) {
    return _buildExpressionWithRole(hasRolePrefix, roleAuthority);
  }

  /// Returns the expression like hasAnyRole('ROLE_USER','ROLE_ADMIN').
  /// [roleAuthorities] is the list string of role authority. AKA role name.
  static String hasAnyRole(List<String> roleAuthorities) {
    return _buildExpressionWithRoles(hasAnyRolePrefix, roleAuthorities);
  }

  /// Returns the expression like hasNoRole('ROLE_ADMIN').
  /// [roleAuthority] is the string of role authority. AKA role name.
  static String hasNoRole(String roleAuthority) {
    return _buildExpressionWithRole(hasNoRolePrefix, roleAuthority);
  }

  /// Returns the expression like hasNoAnyRole('ROLE_USER','ROLE_ADMIN').
  /// [roleAuthorities] is the list string of role authority. AKA role name.
  static String hasNoAnyRole(List<String> roleAuthorities) {
    return _buildExpressionWithRoles(hasNoAnyRolePrefix, roleAuthorities);
  }

  static bool checkRouteOnConfigAttributes(
    User? user,
    String route,
    Map<String, String> configAttributes,
  ) {
    String configuredRoute;
    String tempRoute;
    var granted = false;

    for (final entry in configAttributes.entries) {
      // First of all we need to remove trailing wildcard.
      // For example, item.Url is /user/** or /user/*.
      // configuredUrl is /user.
      configuredRoute = entry.key.replaceAll("/**", "");
      configuredRoute = configuredRoute.replaceAll("/*", "");

      // Removes the configuredUrl from urlWithoutRoot.
      // Before removing, /user/get.
      // After removing, /get.
      tempRoute = route.replaceAll(configuredRoute, "");

      if (tempRoute.isEmpty) {
        // If tempUrl is empty that means the url is exactly matched with configuredUrl.
        // Then we need to check on item.ConfigAttribute.
        granted = checkRouteOnConfigAttribute(user, entry.value);

        // If we find exactly match we just need to stop.
        break;
      }

      if (tempRoute == route) {
        // If configuredUrl is /user/status then tempUrl will be the same as urlWithoutRoot.
        // That means a mismatch. We need to check on next.
        continue;
      }

      if (!tempRoute.startsWith("/")) {
        // If tempUrl does NOT start with "/" that means the parent url does not fully match.
        // For example
        // configuredUrl: /measuringData
        // urlWithoutRoot: /measuringDataSharing/queryValidDetails
        // tempUrl will be Sharing/queryValidDetails
        continue;
      }

      // Checks if url starts with tempUrl.
      // For example /authentication is configured but NOT /api/authentication.
      // We only check wildcard config if url ends with tempUrl.
      if (!route.startsWith(tempRoute)) {
        if (entry.key.endsWith(urlDoubleWildcard)) {
          // If tempUrl is /get or /status/check it would also match the double wildcard.
          // Then we need to check on item.ConfigAttribute.
          granted = checkRouteOnConfigAttribute(user, entry.value);
        } else if (entry.key.endsWith(urlSingleWildcard)) {
          // If tempUrl is /get it can match the single wildcard. But /status/check does NOT.
          // Then we need to check on item.ConfigAttribute.
          if (tempRoute.contains("/")) {
            granted = checkRouteOnConfigAttribute(user, entry.value);
          }
        }
      }
    }

    return granted;
  }

  /// Checks requested url access permission on security configAttribute.
  static bool checkRouteOnConfigAttribute(User? user, String configAttribute) {
    var granted = false;

    if (configAttribute.isEmpty) {
      return false;
    }

    if (configAttribute.startsWith(denyAll)) {
      granted = false;
    } else if (configAttribute.startsWith(permitAll)) {
      granted = true;
    } else if (configAttribute.startsWith(isAuthenticatedPrefix) &&
        user != null) {
      granted = true;
    } else if ((configAttribute.startsWith(hasRolePrefix) ||
            configAttribute.startsWith(hasAnyRolePrefix)) &&
        user != null) {
      granted = RouteMapService.containsAnyRoleInConfigAttribute(
        List.from(user.fullRoles!.map((e) => e.authority)),
        configAttribute,
      );
    } else if ((configAttribute.startsWith(hasNoRolePrefix) ||
            configAttribute.startsWith(hasNoAnyRolePrefix)) &&
        user != null) {
      granted = !RouteMapService.containsAnyRoleInConfigAttribute(
        List.from(user.fullRoles!.map((e) => e.authority)),
        configAttribute,
      );
    }

    return granted;
  }

  /// Returns the expression like hasRole('ROLE_ADMIN').
  /// [prefix] is string of expression prefix. Like hasRole, hasNoRole.
  /// [roleAuthority] is the string of role authority. AKA role name.
  static String _buildExpressionWithRole(String prefix, String roleAuthority) {
    return '$prefix(\'$roleAuthority\')';
  }

  /// Returns the expression like hasAnyRole('ROLE_USER','ROLE_ADMIN').
  /// [prefix] is string of expression prefix. Like hasRole, hasNoRole.
  /// [roleAuthorities] is the list string of role authority. AKA role name.
  static String _buildExpressionWithRoles(
    String prefix,
    List<String> roleAuthorities,
  ) {
    // The initial string of the expression.
    String result = '$prefix(';

    // Goes through the list of roleAuthorities and appends it to result.
    for (final p in roleAuthorities) {
      result += '\'$p\',';
    }

    // Removes the last comma and closes with closing bracket.
    result = '${result.substring(0, result.length - 1)})';

    return result;
  }
}
